package com.ditty.kit;

import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Enumeration;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.ditty.clazz.IClassHandler;
import com.ditty.common.IFilter;

/**
 * 类加载器工具类
 * 
 * @author dingnate
 */
public class ClassLoaderKit {
	private static final Logger LOG = LoggerFactory.getLogger(ClassLoaderKit.class);
	private static final String SUN_MISC_LAUNCHER$_EXT_CLASS_LOADER = "sun.misc.Launcher$ExtClassLoader";

	ClassLoaderKit() {
	}

	public static void scanClass(IFilter<String> jarNameFilter, IFilter<String> packageFilter, IClassHandler... classHandlers) {
		for (URL classPathUrl : getClassPathUrls()) {
			String path = classPathUrl.getFile();
			if (path.startsWith(ValueKit.STR_SLASH)) {
				path = path.substring(1);
			}
			if (path.endsWith(ValueKit.SUFFIX_JAR)) {
				if (jarNameFilter == null || jarNameFilter.accept(path.substring(0, path.lastIndexOf(ValueKit.CHAR_SLASH)))) {
					scanClassInJar(path, packageFilter, classHandlers);
				}
			} else {
				scanClassInFile(path, packageFilter, classHandlers);
			}
		}
	}

	private static void scanClassInFile(final String path, final IFilter<String> packageFilter, final IClassHandler... classHandlers) {
		FileKit.list(new File(path), new FileFilter() {
			@Override
			public boolean accept(File pathname) {
				String entryName = pathname.getAbsolutePath().substring(path.length());
				if (pathname.isDirectory() || !entryName.endsWith(ValueKit.SUFFIX_CLASS)) {
					return false;
				}
				String className = entryName.substring(0, entryName.length() - 6).replace(File.separator, ValueKit.STR_DOT);
				if (packageFilter == null || packageFilter.accept(className)) {
					for (IClassHandler classHandler : classHandlers) {
						try {
							classHandler.handle(Class.forName(className, false, getClassLoader()));
						} catch (Throwable ex) {
							LOG.error("scan class error :" + pathname, ex);
						}
					}
				}
				return false;
			}
		}, true);
	}

	private static void scanClassInJar(String filePath, IFilter<String> packageFilter, IClassHandler... classHandlers) {
		JarFile jarFile = null;
		try {
			filePath = URLDecoder.decode(filePath, "UTF-8");
			jarFile = new JarFile(filePath);
			Enumeration<JarEntry> entries = jarFile.entries();
			while (entries.hasMoreElements()) {
				JarEntry jarEntry = entries.nextElement();
				String entryName = jarEntry.getName();
				if (!jarEntry.isDirectory() && entryName.endsWith(ValueKit.SUFFIX_CLASS)) {
					String className = entryName.replace(ValueKit.STR_SLASH, ValueKit.STR_DOT).substring(0, entryName.length() - 6);
					try {
						if (packageFilter == null || packageFilter.accept(className)) {
							for (IClassHandler classHandler : classHandlers) {
								classHandler.handle(Class.forName(className, false, getClassLoader()));
							}
						}
					} catch (NoClassDefFoundError err) {
						//do nothing
					} catch (Throwable ex) {
						LOG.error("scan class error :" + filePath + "/" + entryName, ex);
					}
				}
			}
		} catch (IOException e) {
			LOG.error("scan class error :" + filePath, e);
		} finally {
			if (jarFile != null) {
				try {
					jarFile.close();
				} catch (IOException e) {
				}
			}
		}
	}

	private static Collection<URL> getClassPathUrls() {
		ArrayList<URL> list = new ArrayList<URL>();
		findUrls(list, getClassLoader());
		return list;
	}

	public static ClassLoader getClassLoader() {
		ClassLoader ret = Thread.currentThread().getContextClassLoader();
		return ret != null ? ret : ClassLoaderKit.class.getClassLoader();
	}

	private static void findUrls(Collection<URL> classPathUrls, ClassLoader classLoader) {
		if (classLoader instanceof URLClassLoader) {
			classPathUrls.addAll(Arrays.asList(((URLClassLoader) classLoader).getURLs()));
			ClassLoader parent = classLoader.getParent();
			if (parent == null || parent.getClass().getName().equals(SUN_MISC_LAUNCHER$_EXT_CLASS_LOADER)) {
				return;
			}
			findUrls(classPathUrls, parent);
		}
	}
}
